/*******************************************************************************
 * Copyright (c) 2012, 2013 XML-Print Project.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     XML-Print Project - initial API and implementation
 ******************************************************************************/
package info.xmlprint.core.format;

import info.xmlprint.core.referencesystem.ReferencingSystemGroup;
import info.xmlprint.ui.widgets.IGroup;
import info.xmlprint.util.FileUtil;
import info.xmlprint.core.referencesystem.ReferenceFieldGroup;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

/*
 * ein Xml hat ein FormatGroup
 * ein FormatGroup hat viele Formate
 */
public class FormatGroup implements Serializable, IGroup {
	/**
	 * 
	 */
	private static final long serialVersionUID = 4549794892268978137L;

	private List<Format> formatList = new ArrayList<Format>();

	private Format standard;

	private Format ignore;

	private Map<String, NumberingStyle> numberingStyleList = new HashMap<String, NumberingStyle>();

	private Set<String> fontNameSet = new HashSet<String>();

	private ReferencingSystemGroup referencingSystemGroup;
	private ReferenceFieldGroup referenceFieldGroup;

	public FormatGroup() {
		initNumberingStyle();
	}

	public void addFontName(String fontName) {
		if (fontName != null && !fontName.isEmpty())
			fontNameSet.add(fontName);
	}

	public void addFormat(Format format) {
		if (format.getType().equals("standard"))
			setStandardFormat(format);
		else if (format.getType().equals("ignore"))
			setIgnoreFormat(format);

		format.setFormatGroup(this);
		formatList.add(format);
		addFontName(format.getAttribute("font-family"));
	}

	/*
	 * 
	 */
	public void addNumberingStyle(NumberingStyle ns) {
		if (!ns.getText().equals(""))
			numberingStyleList.put(ns.getText(), ns);
	}

	/**
	 * This method is used to convert a Document to a String
	 * 
	 * @param document
	 *            - Document object which has to be converted to a string object
	 * @return
	 */
	public String convertDocument2String(Document document) {
		String xml = null;
		xml = document.asXML();
		return xml;
	}

	public Document convertString2Document(String xml) {
		Document document = null;
		try {
			document = DocumentHelper.parseText(xml);
		} catch (DocumentException e) {
			Logger.getLogger(this.getClass().getName()).error("", e);
			e.printStackTrace();
		}
		return document;
	}

	public int getCount() {
		return formatList.size();
	}

	public Set<String> getFontNameSet() {
		return fontNameSet;
	}

	public Format getFormatByName(String name) {
		if (name != null)
			for (Format f : formatList) {
				if (f.getName().equals(name))
					return f;
			}

		return null;
	}

	public String[] getFormatNames() {
		String[] str = new String[formatList.size()];
		int i = 0;
		for (Format f : formatList) {
			str[i] = f.getName();
			i++;

		}
		java.util.Arrays.sort(str);
		return str;
	}

	public Format[] getFormats() {
		int i = 0;

		Collections.sort(formatList, new Comparator<Format>() {
			public int compare(Format f0, Format f1) {
				return f0.getName().compareTo(f1.getName());
			}
		});

		Format[] formats = new Format[formatList.size()];

		for (String fName : getFormatNames()) {
			formats[i] = getFormatByName(fName);
			i++;
		}

		return formats;
	}

	public Format getIgnoreFormat() {
		return ignore;
	}

	/*
	 * 
	 */
	public NumberingStyle getNumberingStyle(String key) {
		return numberingStyleList.get(key);
	}

	// 1,2,3... / a,b,c....
	public String[] getNumberingStyleItems() {
		String[] str = new String[numberingStyleList.size()];
		int i = 0;
		for (String key : numberingStyleList.keySet()) {
			str[i] = numberingStyleList.get(key).getText();
			i++;
		}

		return str;
	}

	public ReferencingSystemGroup getReferencingSystemGroup() {
		return referencingSystemGroup;
	}

	public Format getStandardFormat() {
		return standard;
	}

	/*
	 * Formate zum Xml
	 * 
	 * @return dom4j-Document
	 */
	public Document getXmlDoc() {
		Document doc = DocumentHelper.createDocument();

		Element root = doc.addElement("formats");

		for (Format format : getFormats()) {

			Element e = root.addElement("format");

			// id
			e.addElement("id").setText(format.getId());

			// name
			e.addElement("name").setText(format.getName());

			// inline, block, footnote ....
			e.addElement("fo-category").setText(format.getFoCategory());

			// type
			e.addElement("type").setText(format.getType());

			// footnote Attribute
			for (String s : format.getFootnoteAttributNames()) {
				e.addElement(s).setText(format.getFootnoteAttribute(s));
			}
			
			// Formatierungsinformationen: margin, font, optimum...
			for (String s : format.getAttributeNames()) {
				e.addElement(s).setText(format.getAttribute(s));
				e.addElement("inherit_" + s).setText(
						format.getInherit(s).toString());
			}
		}

		return doc;
	}

	/*
	 * Formate zum Xsl hier werden die Formate in die doc eingeschrieben, um ein
	 * Xsl zu erzeugen
	 * 
	 * @param doc: doc aus StyleIO,enthaeht die Zuordnungen von Tag-Format
	 * 
	 * @return: dom4j-Document
	 */
	public Document getXslDoc(Document doc) {

		Element root = doc.getRootElement();
		// hier werden Type und fo-category von Format nicht gebraucht,
		// weil dies durch MappingGroup verwaltet,
		// hier werden nur Attribute-Werte von Format genommen
		// und steht fuer MappingGroup bereit

		for (Format format : getFormats()) {
			Element set = root.addElement("xsl:attribute-set");
			set.addAttribute("name", format.getFormatedName());

			Element columnset = root.addElement("xsl:attribute-set");
			columnset.addAttribute("name", format.getFormatedName()
					+ "-columns");

			String s;
			for (String key : format.getAttributeNames()) {
				s = format.getAttribute(key);

				if (s.equals("")) // skip empty attributes
					continue;
				if (key.startsWith("column")) { // store column attributes
					if (!format.isInherit(key)) {
						Element columnattr = columnset
								.addElement("xsl:attribute");
						columnattr.addAttribute("name", key);
						columnattr.setText(s);
					}
				} else {
					if (!format.isInherit(key)) { // ||
													// format.getType().equals("standard"))
													// { //nur Attribute
													// schreiben,
													// falls nicht vererbt oder
													// Standard-Format
						// TODO: Fix this quick and dirty hack, so that there is
						// a
						// general mechanism for attribute not meant for the
						// FO-Export
						if (!key.equals("text-addition-before")
								&& !key.equals("text-addition-after") && !key.equals("pagemaster")) {
							Element attr = set.addElement("xsl:attribute");
							attr.addAttribute("name", key);
							attr.setText(s);
						}
					}
				}
			}
		}

		// für fo:flow brauchen wir das Standardformat ohne evtl. Vererbungen.
		// Daher wird ein zusätzliches Format eingelegt.
		Element set = root.addElement("xsl:attribute-set");
		set.addAttribute("name", "standard");

		String s;
		for (String key : getStandardFormat().getAttributeNames()) {
			s = getStandardFormat().getAttribute(key);
			if (s.equals("") || key.equalsIgnoreCase("pagemaster")
					|| key.equals("text-addition-after") || key.equals("text-addition-before")) // skip empty attributes
				continue;
			
			Element attr = set.addElement("xsl:attribute");
			attr.addAttribute("name", key);
			attr.setText(s);
		}

		StringTokenizer tokenizer = new StringTokenizer(
				convertDocument2String(doc), System.getProperty(
						"line.separator", "\n"));
		StringBuffer newResult = new StringBuffer();
		while (tokenizer.hasMoreTokens()) {
			newResult.append(tokenizer.nextToken().replaceAll("\\s+", " "));
		}

		/*
		 * doc = convertString2Document (newResult.toString()); OutputFormat
		 * format = OutputFormat.createPrettyPrint();
		 * 
		 * StringWriter xsltNeu = new StringWriter(); XMLWriter writer = new
		 * XMLWriter( xsltNeu, format ); try { writer.write( doc ); } catch
		 * (IOException e1) { // TODO Auto-generated catch block
		 * e1.printStackTrace(); }
		 * 
		 * return convertString2Document(xsltNeu.toString());
		 */
		return convertString2Document(newResult.toString());

		// return doc;
	}

	@Override
	public boolean hasElement(String name) {
		if (getFormatByName(name) != null)
			return true;
		return false;
	}

	public boolean hasFormat(Format format) {
		return formatList.contains(format);
	}

	/*
	 * 
	 */
	public boolean hasNumberingStyle(NumberingStyle ns) {
		return numberingStyleList.containsKey(ns.getText());
	}

	private void initNumberingStyle() {
		NumberingStyle ns1 = new NumberingStyle();
		ns1.setId(0);
		ns1.setStart("1");
		ns1.setText("1, 2, 3, ...");
		addNumberingStyle(ns1);

		NumberingStyle ns2 = new NumberingStyle();
		ns2.setId(1);
		ns2.setStart("a");
		ns2.setText("a, b, c, ...");
		addNumberingStyle(ns2);

		NumberingStyle ns3 = new NumberingStyle();
		ns3.setId(2);
		ns3.setStart("i");
		ns3.setText("i, ii, iii, ...");
		addNumberingStyle(ns3);

		/*
		 * NumberingStyle ns4 = new NumberingStyle(); ns4.setId(3);
		 * ns4.setStart(""); ns4.setText("Referenz"); addNumberingStyle(ns4);
		 */
	}

	public FormatGroup objClone() {
		FormatGroup newFg = (FormatGroup) FileUtil.objClone(this);
		return newFg;
	}

	public static FormatGroup readDocNode(Node root) {
		FormatGroup newFg = new FormatGroup();

		// TODO prüfen, ob xml kompatible ist
		{

		}

		for (Object ob : root.selectNodes("format").toArray()) {

			Node node = (Node) ob;

			Format format = new Format();

			// Name
			format.setName(node.selectSingleNode("name").getText());

			// id
			format.setId(node.selectSingleNode("id").getText());

			// inline, block, footnote ....
			format.setFoCategory(node.selectSingleNode("fo-category").getText()
					.trim());

			// Footnote
			{

				// fn-numberingstyle, fn-prefix, fn-postfix...
				// TODO hier soll abgefragt werden, ob Footnote überschreiben
				// oder beibehalten
				String fnstr;
				for (String key : format.getFootnoteAttributNames()) {
					Node n = node.selectSingleNode(key);
					fnstr = "";
					if (n != null && n.getText() != null
							&& !n.getText().isEmpty())
						fnstr = n.getText();

					format.setFootnoteAttribute(key, fnstr);
				}

			}

			// Type
			String type = node.selectSingleNode("type").getText();
			format.setType(type);

			// Formatierungen
			{
				String str;
				// Attribute, die mit Formatierung zu tun haben: font, margin,
				// usw.
				for (String s : format.getAttributeNames()) {
					Node n = node.selectSingleNode(s);
					str = "";
					if (n != null && n.getText() != null
							&& !n.getText().isEmpty())
						str = n.getText();

					format.setAttribute(s, str);

					n = node.selectSingleNode("inherit_" + s);
					str = "";
					if (n != null && n.getText() != null
							&& !n.getText().isEmpty())
						str = n.getText();

					if (str.equals("true")) {
						format.setInherit(s, true);
					} else {
						format.setInherit(s, false);
					}
				}
			}

			newFg.addFormat(format);

			// stardardformat/ignore des FormatGroup definieren
			if (type.equals("standard"))
				newFg.setStandardFormat(format);
			else if (type.equals("ignore"))
				newFg.setIgnoreFormat(format);

		}
		return newFg;

	}

	/*
	 * Xml einlesen, eine Liste von Formate wird erzeugt
	 */
	public static FormatGroup readFile(String filePath) {
		SAXReader reader = new SAXReader();
		File file = new File(filePath);
		if (file.exists()) {
			try {
				Document doc = reader.read(file);
				Node root = doc.getRootElement();
				return  readDocNode(root); 
			} catch (DocumentException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	public void removeAll() {
		formatList.clear();
	}

	public void removeFormat(Format f) {
		formatList.remove(f);
	}

	/*
	 * 
	 */
	public void removeNumberingStyle(NumberingStyle ns) {
	}

	/*
	 * Xml speichern
	 */
	public boolean saveToXml(String filePath) {
		File file = new File(filePath);
		Document doc = getXmlDoc();

		try {
			OutputFormat of = new OutputFormat();
			of.setIndent(true);
			of.setNewlines(true);
			Writer writer = new OutputStreamWriter(new FileOutputStream(file),
					"UTF-8");
			XMLWriter xmlWriter = new XMLWriter(writer, of);
			xmlWriter.write(doc);
			xmlWriter.close();
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}

	}

	public void setIgnoreFormat(Format ignore) {
		this.ignore = ignore;
	}

	// public void setReferencingSystemGroup(
	// ReferencingSystemGroup referencingSystemGroup) {
	// this.referencingSystemGroup = referencingSystemGroup;
	// }

	public void setStandardFormat(Format standard) {
		this.standard = standard;
	}

	public void toPrintln() {
		for (Format f : formatList) {
			System.out.println(f);
		}
	}

	public ReferenceFieldGroup getReferenceFieldGroup() {
		return referenceFieldGroup;
	}

}
